package com.yin.servlet;

import com.yin.annotation.YinController;
import com.yin.annotation.YinRequestMapping;
import com.yin.annotation.YinRequestParam;
import com.yin.context.YinApplicationContext;
import com.yin.webmvc.HandlerAdapter;
import com.yin.webmvc.HandlerMapping;
import com.yin.webmvc.ModelAndView;
import com.yin.webmvc.ViewResolver;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class YinDispatcherServlet2 extends HttpServlet {

    //private Map<String,HandlerMapping> handlerMapping = new HashMap<>();
    private List<HandlerMapping> handlerMappings = new ArrayList<>();
    private Map<HandlerMapping,HandlerAdapter> handlerAdapters = new HashMap<>();
    private List<ViewResolver> viewResolvers = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        YinApplicationContext context = new YinApplicationContext(config.getInitParameter("contextConfigLocation"));
        this.initStrategies(context);
    }

    protected void initStrategies(YinApplicationContext context){
        //this.initMultipartResolver(context);
       // this.initLocaleResolver(context);
        //this.initThemeResolver(context);
        //handlerMapping: pattern, controller, method
        this.initHandlerMappings(context);

        this.initHandlerAdapters(context);
        //this.initHandlerExceptionResolvers(context);
        //this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        //this.initFlashMapManager(context);
    }

    private void initHandlerMappings(YinApplicationContext context) {
        String[] beanNames = context.getBeanDefinitionNames();
        for(String beanName : beanNames){
            Object controller = context.getBean(beanName);
            Class clazz = controller.getClass();
            if(!clazz.isAnnotationPresent(YinController.class)) continue;
            String baseUrl = "";
            if(clazz.isAnnotationPresent(YinRequestMapping.class)){
                YinRequestMapping mapping = (YinRequestMapping) clazz.getAnnotation(YinRequestMapping.class);
                baseUrl = mapping.value();

            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods){
                if(!method.isAnnotationPresent(YinRequestMapping.class)) continue;
                YinRequestMapping requestMapping = method.getAnnotation(YinRequestMapping.class);
                String url = ("/" + baseUrl + "/" +requestMapping.value()).replaceAll("/+","/");
                Pattern pattern = Pattern.compile(url);
                this.handlerMappings.add(new HandlerMapping(pattern,controller,method));
            }
        }
    }

    private void initHandlerAdapters(YinApplicationContext context){
        for(HandlerMapping mapping : this.handlerMappings){
            Map<String,Integer> paramMapping = new HashMap<>();
            Annotation[][] pas = mapping.getMethod().getParameterAnnotations();
            for(int i = 0; i < pas.length; i++){
               for(Annotation annotation : pas[i]){
                   if(annotation instanceof YinRequestParam){
                       String paramName = ((YinRequestParam) annotation).value();
                       if(!"".equals(paramName.trim())){
                           paramMapping.put(paramName,i);
                       }
                   }
               }

            }
            //非命名参数
            Class[] paramTypes = mapping.getMethod().getParameterTypes();
            for(int i = 0; i < paramTypes.length; i++){
                Class type = paramTypes[i];
                if(type == HttpServletRequest.class ||
                        type == HttpServletResponse.class){
                    paramMapping.put(type.getName(),i);
                }
            }
            this.handlerAdapters.put(mapping,new HandlerAdapter(paramMapping));
        }

    }
    private void initViewResolvers(YinApplicationContext context){

        String templateRoot = context.getConfig().getProperty("templateRoot");
        String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
        File templateRootDir = new File(templateRootPath);
        for(File template : templateRootDir.listFiles()){
            this.viewResolvers.add(new ViewResolver(template.getName(),template));
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            this.doDispatch(req,resp);
        }catch (Exception e){
            resp.getWriter().write("500 exception");
        }
      
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{

      
           HandlerMapping mapping = getHandler(req);
           if(mapping == null){
               resp.getWriter().write("<h1>404 not found</h1>");
               return;
           }
           HandlerAdapter ha = getHandlerAdapter(mapping);
           ModelAndView mv = ha.handle(req,resp,mapping);
           processDispatchResult(resp,mv); 
           
    }

    private void processDispatchResult(HttpServletResponse resp, ModelAndView mv) throws Exception {
        //调用viewResolver的resolveView方法
        if(null == mv){ return;}

        if(this.viewResolvers.isEmpty()){ return;}

        for (ViewResolver viewResolver: this.viewResolvers) {

            if(!mv.getViewName().equals(viewResolver.getViewName())){ continue; }
            String out = viewResolver.viewResolver(mv);
            if(out != null){
                resp.getWriter().write(out);
                break;
            }
        }
    }

    private HandlerAdapter getHandlerAdapter(HandlerMapping mapping) {
        if(this.handlerAdapters.isEmpty()) return null;
        return this.handlerAdapters.get(mapping);
    }

    private HandlerMapping getHandler(HttpServletRequest req) {
        if(this.handlerMappings.isEmpty()) return null;
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");
        for(HandlerMapping handlerMapping : this.handlerMappings){
            Matcher matcher = handlerMapping.getUrl().matcher(url);
            if(!matcher.matches()) continue;
            return handlerMapping;
        }
        return null;
    }
}
